/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/proto_udp.c 
 * @date        2010-04-05
 * @brief	general_network
 *		udp implememtation of generate-operations
 * @see         dserver
 * @history
 */

#include <unistd.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/sendfile.h>
#include <sys/mman.h>
#include <event.h>
#include <poll.h>

#include <dlib/mempool.h>

#include "dserver_log.h"
#include "dserver_errno.h"
#include "utils.h"
#include "connection.h"
#include "prot_types.h"

#ifndef POLLWRNORM
#define	POLLWRNORM	POLLOUT
#endif

/** udp server callbacks */
static int udp_server_init(const char *domain, in_port_t port, uint16_t timeo);
static int udp_server_exit(int sockfd);
static void udp_server_data_maker(int fd, short flag, void *arg);
static void udp_server_data_handler(int fd, short flag, void *arg);
static int udp_server_data_exit(int sockfd);

prot_ops_t udp_ops = {
	.p_server_init = udp_server_init,
	.p_server_exit = udp_server_exit,
	.p_server_conn_handler = NULL,
	.p_server_conn_exit = NULL,
	.p_server_data_maker = udp_server_data_maker,
	.p_server_data_handler = udp_server_data_handler,
	.p_server_data_exit = udp_server_data_exit,
};

/** udp server functions */
static int udp_server_init(const char *domain, in_port_t port, uint16_t timeo)
{
	int sock = dserver_server_new(domain, port, PROTOCOL_UDP);
	if (sock > 0 && timeo > 0) {
		(void)dserver_set_rcv_timeout(sock, timeo, 0);
		(void)dserver_set_snd_timeout(sock, timeo, 0);
	}
	return sock;
}

static int udp_server_exit(int sockfd)
{
	return close(sockfd);
}

static void udp_server_data_maker(int fd, short flag, void *arg)
{
	ssize_t size;
	prot_node_t *prot = (prot_node_t *)arg;
	dserver_conn_t *conn = prot->p_base.p_udp_main_conn;

	UNUSED_PARAM(fd);
	UNUSED_PARAM(flag);

	/* if (flag == EV_TIMEOUT) {
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_WARNING(LOG_MAIN, "prot[%s:%d] write data timeout.",
				prot->p_name, prot->p_sfd);
#endif
		return;
	} */

	size = sendto(prot->p_sfd, conn->c_wbuf, conn->c_wsize, 0,
			&conn->c_csa, conn->c_csa_len);
	if (size < 0) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		if (errno != EAGAIN && errno != EWOULDBLOCK) {
#else
		if (errno != EAGAIN) {
#endif
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_ERR(LOG_MAIN, "prot[%s:%d] udp send data error: %s",
					prot->p_name, prot->p_sfd, strerror(errno));
#endif
			return;
		} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_ERR(LOG_MAIN, "prot[%s:%d] udp send data blocked: %s",
					prot->p_name, prot->p_sfd, strerror(errno));
#endif
			return;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		}
#else
		} /* EAGAIN */
#endif
	} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] udp send data[%d]",
				prot->p_name, prot->p_sfd, size);
#endif
		if ((uint32_t)size < conn->c_wsize) {
			conn->c_wsize -= size;
			/* overlap copy */
			memmove(conn->c_wbuf, conn->c_wbuf + size, conn->c_wsize);
			conn->c_wbuf[conn->c_wsize] = 0;

			/* do data send again */
			(*prot->p_ops.p_server_data_maker)(prot->p_sfd, EV_WRITE, (void *)prot);
		}
	}

	return;
}

static void udp_server_data_handler(int fd, short flag, void *arg)
{
	ssize_t size = 0;
	prot_ret_t ret = PROTOCOL_CLOSE;
	prot_node_t *prot = (prot_node_t *)arg;
	dserver_conn_t *conn = prot->p_base.p_udp_main_conn;

	UNUSED_PARAM(fd);
	UNUSED_PARAM(flag);

	dserver_conn_cleanup(conn); 
	conn->c_prot_node = prot;

	if (prot->s_ops.p_session_init != NULL) {
		if ((*prot->s_ops.p_session_init)((void *)conn) < 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_DEBUG(LOG_MAIN, "protocol[%s:udp] session init error.", prot->p_name);
#endif
			return;
		}
	} /* p_session_init */

	while (1) {
		size = recvfrom(prot->p_sfd, conn->c_rbuf, DSERVER_RX_BUFLEN - 1, 0,
				&conn->c_csa, &conn->c_csa_len);
		if (size < 0) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
#else
			if (errno != EAGAIN) {
#endif
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_ERR(LOG_MAIN, "prot[%s:%d] udp read data error: %s",
						prot->p_name, prot->p_sfd, strerror(errno));
#endif
				break; /* read error return */
			} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] udp read blocked: %s",
						prot->p_name, prot->p_sfd, strerror(errno));
#endif
				break; /* no data for reading on event */
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			} /* end if block */
#else
			}
#endif
		} else if (size == 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] udp read 0(peer shutdowned).",
					prot->p_name, prot->p_sfd);
#endif
			break; /* received FIN and return */ 
		} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] udp read data[%d].",
					prot->p_name, prot->p_sfd, size);
#endif
			conn->c_rsize = size;
			conn->c_rbuf[conn->c_rsize] = 0;

			/** do protocol parser */
			if (prot->s_ops.p_session_worker != NULL) {
				ret = (*prot->s_ops.p_session_worker)((void *)conn);
			} else {
				ret = PROTOCOL_SUCCESS; /* default */
			}

			if (ret == PROTOCOL_SUCCESS) { /* continue */
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "UDP prot[%s:%d] return success.",
					prot->p_name, conn->c_cfd);
#endif
				break;
			} else if (ret == PROTOCOL_IGNORE) { /* udp ignore means normally exit */
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "UDP prot[%s:%d] return ignore.",
					prot->p_name, conn->c_cfd);
#endif
				break;
			} else if (ret == PROTOCOL_CLOSE) { /* may lost something */
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "UDP prot[%s:%d] return close.",
					prot->p_name, conn->c_cfd);
#endif
				return; /* return directively, no-executing p_session_exit */
			} else { /* error */
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "UDP prot[%s:%d] return error.",
					prot->p_name, conn->c_cfd);
#endif
				break;
			}
		} /* read size */
	} /* while true */

	/** finished a session */
	if (prot->s_ops.p_session_exit != NULL) {
		(*prot->s_ops.p_session_exit)((void *)conn);
	}

	return;
}

static int udp_server_data_exit(int sockfd)
{
	return shutdown(sockfd, SHUT_WR);
}

